package com.aliothservice.catfish.device;

import java.util.HashMap;
import java.util.Map;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.redis.core.RedisTemplate;

import com.aliothservice.catfish.SessionManager;
import com.aliothservice.catfish.device.data.DeviceDataManager;
import com.aliothservice.catfish.device.request.VehicleBindingRequestRequest;
import com.aliothservice.catfish.device.request.VehicleHumidityDocumentRequest;
import com.aliothservice.catfish.device.request.VehicleLocationDocumentRequest;
import com.aliothservice.catfish.device.request.VehicleTemperatureDocumentRequest;
import com.aliothservice.catfish.device.request.WarehouseBindingRequestRequest;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.epoll.EpollEventLoopGroup;
import io.netty.channel.epoll.EpollServerSocketChannel;
import io.netty.channel.socket.SocketChannel;

public class DeviceController {
	
	public Map<String, String> vehicleBindingRequest(VehicleBindingRequestRequest req) {
		logger.info("vehicleBindingRequest - " + req.getContactName() + ":" + req.getContactPhoneNumber());
		
		Map<String, String> map = new HashMap<String, String>();
		
		// validate token
		String phoneNumber = (String)sessionManager.valueForRestToken(req.getToken());
		if (null == phoneNumber) {
			map.put("errorCode", "300");
			map.put("errorMessage", "token expired");
			return map;
		}
		
		// validate parameters
		if ((null==req.getPendingId()) || (null==req.getContactName()) || (null==req.getContactPhoneNumber())) {
			map.put("errorCode", "400");
			map.put("errorMessage", "missing parameters");
			return map;
		}
		
		// perform creation of the request
		if (!deviceDataManager.vehicleBindingRequest(phoneNumber, req)) {
			map.put("errorCode", "500");
			map.put("errorMessage", "failed to operate");
			return map;
		}

		// reset token expiration
		sessionManager.resetRestTokenExpiration(phoneNumber, req.getToken());
		
		return map;
	}
	
	public Map<String, String> warehouseBindingRequest(WarehouseBindingRequestRequest req) {
		logger.info("warehouseBindingRequest - " + req.getContactName() + ":" + req.getContactPhoneNumber());
		
		Map<String, String> map = new HashMap<String, String>();
		
		// validate token
		String phoneNumber = (String)sessionManager.valueForRestToken(req.getToken());
		if (null == phoneNumber) {
			map.put("errorCode", "300");
			map.put("errorMessage", "token expired");
			return map;
		}
		
		// validate parameters
		if ((null==req.getPendingId()) || (null==req.getContactName()) || (null==req.getContactPhoneNumber())) {
			map.put("errorCode", "400");
			map.put("errorMessage", "missing parameters");
			return map;
		}
		
		// perform creation of the request
		if (!deviceDataManager.warehouseBindingRequest(phoneNumber, req)) {
			map.put("errorCode", "500");
			map.put("errorMessage", "failed to operate");
			return map;
		}
		
		// reset token expiration
		sessionManager.resetRestTokenExpiration(phoneNumber, req.getToken());
		
		return map;
	}
	
	public Object vehicleLocationDocument(VehicleLocationDocumentRequest req) {
		Map<String, String> map = new HashMap<String, String>();
		
		// validate token
		String phoneNumber = (String)sessionManager.valueForRestToken(req.getToken());
		if (null == phoneNumber) {
			map.put("errorCode", "300");
			map.put("errorMessage", "token expired");
			return map;
		}
		
		// validate parameters
		if ((null==req.getOrderId()) || (null==req.getLongitude()) || (null==req.getLatitude()) || (null==req.getRadius())) {
			map.put("errorCode", "400");
			map.put("errorMessage", "missing parameters");
			return map;
		}
		
		// retrieve document
		Map<String, Object> documentMap = deviceDataManager.vehicleLocationDocument(phoneNumber, req);
		if (null == documentMap) {
			map.put("errorCode", "500");
			map.put("errorMessage", "location document not found");
			return map;
		}
		
		// reset token expiration
		sessionManager.resetRestTokenExpiration(phoneNumber, req.getToken());
		
		return documentMap;
	}
	
	public Object vehicleTemperatureDocument(VehicleTemperatureDocumentRequest req) {
		Map<String, String> map = new HashMap<String, String>();
		
		// validate token
		String phoneNumber = (String)sessionManager.valueForRestToken(req.getToken());
		if (null == phoneNumber) {
			map.put("errorCode", "300");
			map.put("errorMessage", "token expired");
			return map;
		}
		
		// validate parameters
		if (null == req.getOrderId()) {
			map.put("errorCode", "400");
			map.put("errorMessage", "missing parameters");
			return map;
		}
		
		// retrieve document
		Map<String, Object> documentMap = deviceDataManager.vehicleTemperatureDocument(phoneNumber, req);
		if (null == documentMap) {
			map.put("errorCode", "500");
			map.put("errorMessage", "temperature document not found");
			return map;
		}
		
		// reset token expiration
		sessionManager.resetRestTokenExpiration(phoneNumber, req.getToken());
		
		return documentMap;
	}
	
	public Object vehicleHumidityDocument(VehicleHumidityDocumentRequest req) {
		Map<String, String> map = new HashMap<String, String>();
		
		// validate token
		String phoneNumber = (String)sessionManager.valueForRestToken(req.getToken());
		if (null == phoneNumber) {
			map.put("errorCode", "300");
			map.put("errorMessage", "token expired");
			return map;
		}
		
		// validate parameters
		if (null == req.getOrderId()) {
			map.put("errorCode", "400");
			map.put("errorMessage", "missing parameters");
			return map;
		}
		
		// retrieve document
		Map<String, Object> documentMap = deviceDataManager.vehicleHumidityDocument(phoneNumber, req);
		if (null == documentMap) {
			map.put("errorCode", "500");
			map.put("errorMessage", "humidity document not found");
			return map;
		}
		
		// reset token expiration
		sessionManager.resetRestTokenExpiration(phoneNumber, req.getToken());
		
		return documentMap;
	}
	
	@PostConstruct
	public void deviceInit() throws InterruptedException {
		logger.info("starting collector...");
		collectorBootstrap = new ServerBootstrap();
		serverGroup = new EpollEventLoopGroup();
		clientGroup = new EpollEventLoopGroup();
		collectorBootstrap.group(serverGroup, clientGroup);
		collectorBootstrap.channel(EpollServerSocketChannel.class);
		collectorBootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
			@Override
			protected void initChannel(SocketChannel channel) throws Exception {
				channel.pipeline().addLast(new CollectorInboundAdapter(redisTemplate, mongoTemplate), new CollectorTimeoutAdapter(10));
			}
		});
		collectorBootstrap.option(ChannelOption.SO_BACKLOG, 128);
		collectorBootstrap.option(ChannelOption.SO_REUSEADDR, true);
		collectorBootstrap.childOption(ChannelOption.SO_KEEPALIVE, true);
		serverFuture = collectorBootstrap.bind(28888);
		serverFuture.sync();
	}
	
	@PreDestroy
	public void deviceCleanUp() throws InterruptedException {
		logger.info("stopping collector...");
		try {
			clientGroup.shutdownGracefully();
			serverGroup.shutdownGracefully();
			serverFuture.channel().close();
			serverFuture.channel().closeFuture().sync();
		} catch (InterruptedException e) {
			logger.info("InterruptedException - " + e.toString());
		}
	}
		
	@Autowired
	private SessionManager sessionManager;
	
	@Autowired
	private DeviceDataManager deviceDataManager;
	
	@Autowired
	private RedisTemplate<String, Object> redisTemplate;
	
	@Autowired
	private MongoTemplate mongoTemplate;
	
	private ServerBootstrap collectorBootstrap;
	private ChannelFuture serverFuture;
	private EventLoopGroup serverGroup;
	private EventLoopGroup clientGroup;

	private static final Logger logger = LoggerFactory.getLogger(DeviceController.class);
}
